package cn.anecansaitin.firecrafting.common.block.entity;

import cn.anecansaitin.firecrafting.api.common.IResultType;
import cn.anecansaitin.firecrafting.api.common.block.entity.IFireInterfaceEntity;
import cn.anecansaitin.firecrafting.api.common.crafting.IFireRecipe;
import cn.anecansaitin.firecrafting.common.Config;
import cn.anecansaitin.firecrafting.common.block.FireInterface;
import cn.anecansaitin.firecrafting.common.capability.item.SignaledItemStackHandler;
import cn.anecansaitin.firecrafting.common.crafting.ModRecipeTypes;
import cn.anecansaitin.firecrafting.common.menu.FireInterfaceMenu;
import com.google.common.collect.ImmutableList;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.Clearable;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseFireBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.EnergyStorage;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.templates.FluidTank;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.registries.ForgeRegistries;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class FireInterfaceEntity extends BlockEntity implements IFireInterfaceEntity, MenuProvider, Clearable {
    private Block fire = Blocks.AIR;
    private boolean worked;
    private boolean fireChanged;
    private boolean filterMode;
    private int recipeIndex = -1;
    private final int cd = Config.PROCESSING_TIME.get();
    private int cdCounter = cd;
    private final ImmutableList<IResultType> types = ImmutableList.of(IResultType.ITEM, IResultType.FLUID, IResultType.ENERGY);
    private final ItemStackHandler filter = new ItemStackHandler(18);
    private final SignaledItemStackHandler itemIn = new SignaledItemStackHandler(18) {
        @Override
        protected void onContentsChanged(int slot) {
            super.onContentsChanged(slot);
        }

        @Override
        public boolean isItemValid(int slot, @NotNull ItemStack stack) {
            if (filterMode) {
                ItemStack inSlot = filter.getStackInSlot(slot);
                return !inSlot.isEmpty() && inSlot.sameItem(stack);
            }
            return true;
        }
    };
    private final LazyOptional<IItemHandler> inHandler = LazyOptional.of(() -> itemIn);
    private final ItemStackHandler itemOut = new ItemStackHandler(9);
    private final LazyOptional<IItemHandler> outHandler = LazyOptional.of(() -> itemOut);
    private final EnergyStorage energyOut = new EnergyStorage(50000, 4096, 4096);
    private final LazyOptional<IEnergyStorage> energyHandler = LazyOptional.of(() -> energyOut);
    private final FluidTank fluidOut = new FluidTank(8000);
    private final LazyOptional<IFluidHandler> fluidHandler = LazyOptional.of(() -> fluidOut);

    public FireInterfaceEntity(BlockPos pos, BlockState state) {
        super(ModBlockEntityTypes.FIRE_INTERFACE_ENTITY.get(), pos, state);
    }

    //每tick触发
    @Override
    public void tick(Level world, BlockPos pos, BlockState state) {
        if (!Config.ENABLE_INTERFACE.get()) {
            return;
        }
        //检测cd是否结束
        if (cdCounter > 0) {
            cdCounter--;
            setChanged();
            return;
        }
        //检测下方为火焰
        boolean fire = checkFire(world, pos);
        //设置方块的发光状态
        if (fire) {
            world.setBlock(pos, state.setValue(FireInterface.LIT, true), 3);
        } else {
            world.setBlock(pos, state.setValue(FireInterface.LIT, false), 3);
        }
        //检测能否工作
        if (fire && (worked || itemIn.isChanged() || fireChanged)) {
            //火焰变化改为false
            fireChanged = false;
            //物品槽变更设为false（表示本次变更已被处理）
            itemIn.setChanged(false);
            //检测过滤器
            if (filterMode) {
                for (int i = 0; i < filter.getSlots(); i++) {
                    ItemStack filterStack = filter.getStackInSlot(i);
                    ItemStack stack = itemIn.getStackInSlot(i);
                    if (!filterStack.isEmpty() && stack.isEmpty()) {
                        //检测其他槽位是否有多余材料并转移过来
                        for (int j = 0; j < itemIn.getSlots(); j++) {
                            if (j == i) {
                                continue;
                            }
                            ItemStack stack1 = itemIn.getStackInSlot(j);
                            if (stack1.sameItem(filterStack) && stack1.getCount() > 1) {
                                if (itemIn.insertItem(i, stack1.copy().split(1), true).isEmpty()) {
                                    itemIn.insertItem(i, stack1.split(1), false);
                                    break;
                                } else {
                                    return;
                                }
                            }
                        }
                    } else {
                        if (filterStack.isEmpty() && stack.isEmpty()) {
                            continue;
                        }
                        if (!filterStack.sameItem(stack)) {
                            return;
                        }
                    }
                }
            }
            //获取所有IFireRecipe类型配方
            List<IFireRecipe> allRecipesFor = world.getRecipeManager().getAllRecipesFor(ModRecipeTypes.FIRE_TYPE.get());
            //检测缓存的配方
            if (recipeIndex >= 0) {
                int doAction = allRecipesFor.get(recipeIndex).doAction(this);
                //依据结果更新
                updateData(doAction);
                //配方符合
                switch (doAction) {
                    case 1:
                        world.sendBlockUpdated(pos, getBlockState(), getBlockState(), 2);
                    case 0:
                        cdCounter = cd;
                        setChanged();
                        return;
                }
            }
            //遍历检测配方
            for (int j = 0, allRecipesForSize = allRecipesFor.size(); j < allRecipesForSize; j++) {
                //按照索引取配方
                IFireRecipe recipe = allRecipesFor.get(j);
                //尝试执行配方，获取结果
                int action = recipe.doAction(this);
                //根据结果进行不同行为
                updateData(action);
                //如果配方检测成功则返回
                if (action == 0 || action == 1) {
                    //将保存索引
                    recipeIndex = j;
                    break;
                }
            }
            world.sendBlockUpdated(pos, getBlockState(), getBlockState(), 2);
        }
        //cd回到初始值
        cdCounter = cd;
        //物品槽变更设为false
        itemIn.setChanged(false);
        setChanged();
    }

    //基于配方行动结果来更新标记和库存
    private void updateData(int actionFlag) {
        switch (actionFlag) {
            case -1 ->
                    //配方不匹配
                    worked = false;
            case 0 ->
                    //配方成功，但无法行动，等待下次执行
                    worked = true;
            case 1 -> {
                //行动成功
                for (int i = 0; i < itemIn.getSlots(); i++) {
                    itemIn.extractItem(i, 1, false);
                }
                worked = true;
            }
        }
    }

    //检测下方火焰，并实时更改存储的火焰
    public boolean checkFire(Level world, BlockPos pos) {
        //获取坐标位置下方的火焰
        BlockState state = world.getBlockState(pos.below());
        Block block = state.getBlock();
        //检测火焰
        if (block instanceof BaseFireBlock) {
            //更新火焰
            if (fire != block) {
                fireChanged = true;
            }

            fire = block;
            return true;
        } else {
            fire = Blocks.AIR;
        }

        return false;
    }

    @Override
    public List<IResultType> actionType() {
        return types;
    }

    //顶面提供物品输入
    //其他面提供物品、流体、能量的输出
    @NotNull
    @Override
    public <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
        //没有面输入
        if (side == null) {
            //返回空
            return LazyOptional.empty();
        }
        //更具面进行返回
        switch (side) {
            case UP:
                //检测询问能力为物品
                if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
                    return inHandler.cast();
                }
                break;
            case DOWN:
            case NORTH:
            case SOUTH:
            case WEST:
            case EAST:
                //根据能力名返回对应能力
                switch (cap.getName()) {
                    case "net/minecraftforge/items/IItemHandler":
                        return outHandler.cast();
                    case "net/minecraftforge/energy/IEnergyStorage":
                        return energyHandler.cast();
                    case "net/minecraftforge/fluids/capability/IFluidHandler":
                        return fluidHandler.cast();
                }
        }
        //均未匹配返回空
        return LazyOptional.empty();
    }

    //获取保存的物品
    @Override
    public List<ItemStack> getMaterial() {
        ArrayList<ItemStack> list = new ArrayList<>();
        //排除空物品
        for (ItemStack item : itemIn.getItems()) {
            if (item.isEmpty()) {
                continue;
            }
            list.add(item);
        }

        return list;
    }

    //获取火焰
    @Override
    public Block getFire() {
        return fire;
    }

    @Override
    public @NotNull <T> LazyOptional<T> getCapForOutput(@NotNull Capability<T> cap) {
        return getCapability(cap, Direction.DOWN);
    }

    @Override
    public @NotNull LazyOptional<IItemHandler> getCapForInput() {
        return getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, Direction.UP);
    }

    @Override
    protected void saveAdditional(@NotNull CompoundTag tag) {
        super.saveAdditional(tag);
        //直接调用已有方法获取需要保存的信息
        getUpdateTag(tag);
    }

    @Override
    public void load(@NotNull CompoundTag tag) {
        super.load(tag);
        try {
            //获取火焰
            fire = ForgeRegistries.BLOCKS.getValue(new ResourceLocation(tag.getString("fire")));
            //获取工作状态
            worked = tag.getBoolean("worked");
            //获取火焰变化状态
            fireChanged = tag.getBoolean("fireChanged");
            //获取配方缓存
            recipeIndex = tag.getInt("recipeIndex");
            //获取cd
            cdCounter = tag.getInt("cdCounter");
            //获取原料
            itemIn.deserializeNBT(tag.getCompound("itemIn"));
            //获取物品产物
            itemOut.deserializeNBT(tag.getCompound("itemOut"));
            //获取能量产物
            energyOut.deserializeNBT(tag.get("energyOut"));
            //获取流体产物
            fluidOut.readFromNBT(tag);
            //获取过滤器
            filter.deserializeNBT(tag.getCompound("filter"));
            //获取过滤器启动状态
            filterMode = tag.getBoolean("filterMode");
        } catch (IllegalArgumentException ignore) {

        }
    }

    @Override
    public @NotNull CompoundTag getUpdateTag() {
        CompoundTag tag = new CompoundTag();
        getUpdateTag(tag);
        return tag;
    }

    public void getUpdateTag(CompoundTag tag) {
        //保存火焰
        tag.putString("fire", Objects.requireNonNull(fire.getRegistryName()).toString());
        //保存工作状态
        tag.putBoolean("worked", worked);
        //保存火焰变化状态
        tag.putBoolean("fireChanged", fireChanged);
        //保存配方缓存
        tag.putInt("recipeIndex", recipeIndex);
        //保存cd
        tag.putInt("cdCounter", cdCounter);
        //保存原料
        tag.put("itemIn", itemIn.serializeNBT());
        //保存物品产物
        tag.put("itemOut", itemOut.serializeNBT());
        //保存能量产物
        tag.put("energyOut", energyOut.serializeNBT());
        //保存流体产物
        fluidOut.writeToNBT(tag);
        //保存过滤器
        tag.put("filter", filter.serializeNBT());
        //保存过滤器启动状态
        tag.putBoolean("filterMode", filterMode);
    }

    @Nullable
    @Override
    public Packet<ClientGamePacketListener> getUpdatePacket() {
        return ClientboundBlockEntityDataPacket.create(this);
    }

    //以下两个为MenuProvider方法，用于提供gui
    //设置展示的名字
    @Override
    public @NotNull Component getDisplayName() {
        return new TranslatableComponent("fire_interface");
    }

    @Nullable
    @Override
    public AbstractContainerMenu createMenu(int id, @NotNull Inventory playerInv, @NotNull Player player) {
        return new FireInterfaceMenu(id, playerInv, getBlockPos());
    }

    public SignaledItemStackHandler getItemIn() {
        return itemIn;
    }

    public ItemStackHandler getItemOut() {
        return itemOut;
    }

    public EnergyStorage getEnergyOut() {
        return energyOut;
    }

    public FluidTank getFluidOut() {
        return fluidOut;
    }

    public ItemStackHandler getFilter() {
        return filter;
    }

    public void switchFilterMode() {
        filterMode = !filterMode;
        worked = true;
    }

    public boolean isFilterMode() {
        return filterMode;
    }

    //通过指令替换方块时将容器内所有物品删除
    @Override
    public void clearContent() {
        itemIn.getItems().clear();
        for (int i = 0; i < itemOut.getSlots(); i++) {
            itemOut.setStackInSlot(i, ItemStack.EMPTY);
        }
    }
}
